﻿<script setup lang="ts">
import type { Editor } from 'tinymce'
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver' // 主题
import 'tinymce/icons/default/icons' // 默认图标
import 'tinymce/models/dom' // 必须引入，否则报错
import 'tinymce/plugins/advlist' // 高级列表
import 'tinymce/plugins/anchor' // 锚点
import 'tinymce/plugins/autolink' // 自动连接
import 'tinymce/plugins/autosave' // 自动保存
import 'tinymce/plugins/charmap' // 特殊字符
import 'tinymce/plugins/code' // 源代码
import 'tinymce/plugins/codesample' // 插入代码示例
import 'tinymce/plugins/directionality' // 文字对齐方向
import 'tinymce/plugins/emoticons' // 表情包
import 'tinymce/plugins/emoticons/js/emojis.js' // 表情包文件
import 'tinymce/plugins/fullscreen' // 全屏
import 'tinymce/plugins/help' // 帮助
import 'tinymce/plugins/image' // 上传图片
import 'tinymce/plugins/importcss' // 引入css 该插件不能放在plugins中
import 'tinymce/plugins/insertdatetime' // 插入时间
import 'tinymce/plugins/link' // 链接
import 'tinymce/plugins/lists' // 列表
// import 'tinymce/plugins/media' // 插入媒体
import 'tinymce/plugins/nonbreaking' // 插入不间断空格
import 'tinymce/plugins/pagebreak' // 插入分页符
import 'tinymce/plugins/preview' // 预览
import 'tinymce/plugins/quickbars' // 快捷工具栏
import 'tinymce/plugins/save' // 保存
import 'tinymce/plugins/searchreplace' // 查找与替换
import 'tinymce/plugins/table' // 表格
import 'tinymce/plugins/template' // 插入模板
import 'tinymce/plugins/visualblocks' // 显示元素范围
import 'tinymce/plugins/visualchars' // 显示不可见字符
import 'tinymce/plugins/wordcount' // 统计字数
import './plugins/formatpainter/plugin.min.js' // 格式刷
import './plugins/indent2em/plugin.min.js' // 首行缩进
import './plugins/letterspacing/plugin.min.js' // 字间距
import './plugins/layout/plugin.min.js' // 一键布局
import './plugins/importword/plugin.min.js' // word文档导入
import './plugins/media/plugin.min.js' // 插入媒体
import 'tinymce/plugins/help/js/i18n/keynav/en.js'
import 'tinymce/plugins/help/js/i18n/keynav/zh_CN.js'
import 'tinymce/plugins/help/js/i18n/keynav/zh_TW.js'

import { unref } from 'vue'
import { toolbarList, pluginsList } from './tinymce'
import { buildShortUUID } from '@/utils/uuid'
import { bindHandlers } from './helper'
import { onMountedOrActivated } from '@/hooks/onMountedOrActivated'
import { isNumber } from '@/utils/is'
import { storeToRefs } from 'pinia'
import { useSystemConfigStore } from '@/store/storage/systemConfigStore'
import { loadEnv } from '^/index'
import { transformI18n } from '@/plugins/i18n'
import { uploadApi } from '@/api/upload'
import { isFunction } from '@/utils/is'
import { errorMessage, warnMessage } from '@/utils/message'
const systemConfigStore = useSystemConfigStore()
const { systemConfig, language } = storeToRefs(systemConfigStore)
const { VITE_PUBLIC_PATH } = loadEnv()

const props = defineProps({
  options: {
    type: Object,
    default: () => {
      return {}
    }
  },
  value: {
    type: String
  },

  toolbar: {
    type: Array,
    default: toolbarList
  },
  plugins: {
    type: String,
    default: pluginsList
  },
  modelValue: {
    type: String
  },
  height: {
    type: [Number, String],
    required: false,
    default: 400
  },
  width: {
    type: [Number, String],
    required: false,
    default: 'auto'
  },
  showImageUpload: {
    type: Boolean,
    default: true
  }
})
const emits = defineEmits(['change', 'update:modelValue', 'inited', 'init-error'])
const { attrs } = getCurrentInstance()
const tinymceId = ref(buildShortUUID('tiny-vue'))
const containerWidth = computed(() => {
  const width = props.width
  if (isNumber(width)) {
    return `${width}px`
  }
  return width
})
const editorRef = ref(null)
const fullscreen = ref(false)
const elRef = ref(null)
const tinymceContent = computed(() => props.modelValue)

const skinName = computed(() => {
  let skinNameStr = 'custom'
  if (language.value === 'zh' || language.value === 'zh_tw') {
    skinNameStr = skinNameStr + '-cn'
  } else {
    skinNameStr = skinNameStr + '-en'
  }
  if (systemConfig.value.isLight) {
    skinNameStr = skinNameStr + '-light'
  } else {
    skinNameStr = skinNameStr + '-dark' + '-' + systemConfig.value.darkTheme.split('#')[1]
  }
  console.log(skinNameStr)
  return skinNameStr
})

const langName = computed(() => {
  const lang = language.value === 'zh' ? 'zh_CN' : language.value === 'en' ? language.value : 'zh_TW'
  return ['zh_CN', 'zh_TW', 'en'].includes(lang) ? lang : 'zh_CN'
})
const fileUpload = async (file, callback, type) => {
  if (!uploadApi || !isFunction(uploadApi)) {
    return warnMessage('upload api must exist and be a function')
  }
  try {
    const result: any = await uploadApi(
      { name: 'file', file: file, filename: file.name, type: 'richText' },
      {
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        config: { showLoading: false, mockEnable: true }
      })
    if (result.status) {
      switch (type) {
        case 'image':
          callback(result.data.url, {
            alt: result.data.name
          })
          break
        case 'media':
          callback(result.data.url, {
            source2: result.data.name
          })
          break
        case 'file':
          callback(result.data.url, {
            text: result.data.name
          })
          break
        default:
          break
      }
    } else {
      console.log(result)
    }
  } catch (e) {
    console.log(e)
  }
}
const initOptions = computed((): any => {
  const { height, options, toolbar, plugins } = props
  const publicPath = VITE_PUBLIC_PATH || '/'
  return {
    selector: `#${unref(tinymceId)}`,
    promotion: false,
    height,
    toolbar,
    toolbar_mode: 'sliding',
    menubar: 'file edit view insert format tools table help', // 菜单栏
    menu: {
      file: { title: 'File', items: 'newdocument restoredraft | print' },
      edit: { title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall | searchreplace' },
      view: { title: 'View', items: 'code | visualaid visualchars visualblocks | preview fullscreen' },
      insert: { title: 'Insert', items: 'image link media template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor | insertdatetime' },
      format: { title: 'Format', items: 'bold italic underline strikethrough superscript subscript indent2em codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | removeformat' },
      tools: { title: 'Tools', items: 'wordcount' },
      table: { title: 'Table', items: 'inserttable | cell row column | advtablesort | tableprops deletetable' },
      help: { title: 'Help', items: 'help' }
    },
    plugins,
    language_url: language.value === 'zh' ? publicPath + 'tinymce/langs/zh_CN.js' : language.value === 'zh_tw' ? publicPath + 'tinymce/langs/zh-Hant.js' : '',
    language: langName.value,
    branding: false,
    default_link_target: '_blank',
    link_title: false,
    object_resizing: false,
    auto_focus: true,
    skin: skinName.value,
    skin_url: publicPath + 'tinymce/skins/ui/' + skinName.value,
    content_css: publicPath + 'tinymce/skins/ui/' + skinName.value + '/content.min.css',
    resize: false,
    min_height: 600,
    font_size_formats: '8px 10px 12px 14px 16px 18px 24px 36px 48px 64px 72px',
    font_family_formats: language.value === 'zh' ? '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino' : 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats',
    icons_url: publicPath + 'tinymce/icons/custom/icons.js',
    icons: 'custom',
    file_picker_types: 'file image media',
    // images_upload_url: '/demo/upimg.php', // 指定一个接受上传文件的后端处理程序地址
    // images_upload_base_path: '/demo', // 后端处理程序地址的根路径
    images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
      const baseUrl = import.meta.env.VITE_BASE_API_PATH
      var file = blobInfo.blob() // 转化为易于理解的file对象
      var maxSize = 2
      const { size, name } = file
      // 设置最大值，则判断
      if (maxSize) {
        if (maxSize > 0 && size / 1024 / 1024 >= maxSize) {
          errorMessage(transformI18n('text.textMaxSizeMultiple', true, [maxSize]))
          return false
        }
      }
      fileUpload(file, resolve, 'image')
    }),
    file_picker_callback: function (callback, value, meta) {
      // 文件分类
      var maxSize = 100
      var filetype = '.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4, .mkv, .avi, .wmv, .rmvb, .mov,.mpg, .mpeg, .webm, .jpg, .jpeg, .png, .gif'
      // 为不同插件指定文件类型及后端地址
      switch (meta.filetype) {
        case 'image':
          filetype = '.jpg, .jpeg, .png, .gif'
          maxSize = 2
          break
        case 'media':
          filetype = '.mp3, .mp4, .mkv, .avi, .wmv, .rmvb, .mov,.mpg, .mpeg, .webm'
          maxSize = 100
          break
        case 'file':
        default:
      }
      // 模拟出一个input用于添加本地文件
      var inputElem = document.createElement('input')
      inputElem.setAttribute('type', 'file')
      inputElem.setAttribute('accept', filetype)
      inputElem.click()
      inputElem.onchange = function () {
        const baseUrl = import.meta.env.VITE_BASE_API_PATH
        var file = inputElem.files[0]
        const { size, name } = file
        // 设置最大值，则判断
        if (maxSize) {
          if (maxSize > 0 && size / 1024 / 1024 >= maxSize) {
            errorMessage(transformI18n('text.textMaxSizeMultiple', true, [maxSize]))
            return false
          }
        }
        fileUpload(file, callback, meta.filetype)
      }
    },
    ...options,
    setup: (editor: Editor) => {
      editorRef.value = editor
      editor.on('init', (e) => {
        console.log(e, 'editor init')
        // e.target.editorCommands.commands.value.fontname = language.value === 'zh' ? () => { return 'Microsoft YaHei' } : () => { return 'verdana' }
        initSetup(e)
      })
    }
  }
})

const disabled = computed(() => {
  const { options } = props
  const getdDisabled = options && Reflect.get(options, 'readonly')
  const editor = unref(editorRef)
  if (editor) {
    editor.setMode(getdDisabled ? 'readonly' : 'design')
  }
  return getdDisabled ?? false
})

watch(
  () => attrs.disabled,
  () => {
    const editor = unref(editorRef)
    if (!editor) {
      return
    }
    editor.setMode(attrs.disabled ? 'readonly' : 'design')
  }
)

onMountedOrActivated(() => {
  if (!initOptions.value?.inline) {
    tinymceId.value = buildShortUUID('tiny-vue')
  }
  nextTick(() => {
    setTimeout(() => {
      initEditor()
    }, 30)
  })
})

onBeforeUnmount(() => {
  destory()
})

onDeactivated(() => {
  destory()
})

watch(
  () => language.value,
  () => {
    destory()
    nextTick(() => {
      setTimeout(() => {
        initEditor()
      }, 30)
    })
  }
)
watch(
  () => systemConfig.value.isLight,
  () => {
    destory()
    nextTick(() => {
      setTimeout(() => {
        initEditor()
      }, 30)
    })
  }
)
watch(
  () => systemConfig.value.darkTheme,
  () => {
    destory()
    nextTick(() => {
      setTimeout(() => {
        initEditor()
      }, 30)
    })
  }
)
const destory = () => {
  if (tinymce !== null) {
    tinymce?.remove?.(unref(initOptions).selector!)
  }
}

const initSetup = (e) => {
  const editor = unref(editorRef)
  if (!editor) {
    return
  }
  const value = props.modelValue || ''
  editor.setContent(value)
  bindModelHandlers(editor)
  bindHandlers(e, attrs, unref(editorRef))
}

const initEditor = () => {
  const el = unref(elRef)
  if (el) {
    el.style.visibility = ''
  }
  tinymce
    .init(unref(initOptions))
    .then((editor) => {
      emits('inited', editor)
    })
    .catch((err) => {
      emits('init-error', err)
    })
}

const setValue = (editor, val, prevVal?: string) => {
  if (
    editor &&
    typeof val === 'string' &&
    val !== prevVal &&
    val !== editor.getContent({ format: attrs.outputFormat })
  ) {
    editor.setContent(val)
  }
}

const bindModelHandlers = (editor) => {
  const modelEvents = attrs.modelEvents ? attrs.modelEvents : null
  const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents

  watch(
    () => props.modelValue,
    (val, prevVal) => {
      setValue(editor, val, prevVal)
    }
  )

  watch(
    () => props.value,
    (val, prevVal) => {
      setValue(editor, val, prevVal)
    },
    {
      immediate: true
    }
  )

  editor.on(normalizedEvents || 'change keyup undo redo', () => {
    const content = editor.getContent({ format: attrs.outputFormat })
    emits('update:modelValue', content)
    emits('change', content)
  })

  editor.on('FullscreenStateChanged', (e) => {
    fullscreen.value = e.state
  })
}
</script>

<template>
  <div class="prefixCls" :style="{ width: containerWidth }">
    <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }"></textarea>
  </div>
</template>

<style lang="scss" scoped>
.prefixCls {
  position: relative;
  line-height: normal;
}

textarea {
  z-index: -1;
  visibility: hidden;
}
</style>
